package com.tencent.sr.iris.activity.common.util;

import com.alibaba.fastjson.JSONObject;
import com.tencent.sr.rmall.common.exception.code.BaseErrorCode;
import com.tencent.sr.rmall.common.exception.retail.TRetailBizException;
import com.tencent.sr.rmall.common.exception.retail.TRetailRpcResultException;
import com.tencent.sr.rmall.springbootstarter.monitor.util.MonitorUtil;
import com.tencent.sr.rmall.springbootstarter.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.http.HttpMethod;
import org.springframework.util.StringUtils;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Calendar;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

@Slf4j
public class OkHttpClient {

    private static final String MAC_NAME = "HmacSHA1";
    private static final String ENCODING = "UTF-8";
    private static final ConnectionPool POOL_CONFIG;
    private static okhttp3.OkHttpClient okHttpClient;

    static {
        POOL_CONFIG = new ConnectionPool(20, 10L, TimeUnit.MINUTES);
        okHttpClient = (new okhttp3.OkHttpClient.Builder()).connectTimeout(15L, TimeUnit.SECONDS)
                .writeTimeout(10L, TimeUnit.SECONDS).readTimeout(20L, TimeUnit.SECONDS)
                .connectionPool(POOL_CONFIG).build();
    }


    public static JSONObject postCommon(String url, String appKey, String appSecret,
            String path, String reqBody, Integer successCode) {
        long startTime = System.currentTimeMillis();
        Throwable throwable = null;
        try {
            log.info("OkHttpClient post url:{},body:{}", url + path, reqBody);
            String contentType = "application/json";
            String xDate = getGMTTime();
            String contentMD5 = base64Encode(getMD5(reqBody).getBytes());

            URL parsedUrl = new URL(url + path);
            String pathAndParams = parsedUrl.getPath();
            if (parsedUrl.getQuery() != null) {
                pathAndParams = pathAndParams + "?" + sortQueryParams(parsedUrl.getQuery());
            }
            String stringToSign = String.format("x-date: %s\n%s\n%s\n%s\n%s\n%s",
                    xDate, HttpMethod.POST.name(), contentType, contentType, contentMD5, pathAndParams);
            byte[] hmacStr = hmacSHA1Encrypt(stringToSign, appSecret);
            String signature = base64Encode(hmacStr);
            String authHeader = String.format("hmac id=\"%s\", algorithm=\"hmac-sha1\", "
                    + "headers=\"x-date\", signature=\"%s\"", appKey, signature);

            RequestBody requestBody = FormBody.create(MediaType.parse("Content-Type=text/plain;charset=utf-8"),
                    reqBody);
            Request request = new Request.Builder()
                    .url(url + path)
                    .post(requestBody)
                    .addHeader("Authorization", authHeader)
                    .addHeader("Accept", contentType)
                    .addHeader("Host", url
                            .replace("http://", "").replace("https://", ""))
                    .addHeader("x-date", xDate)
                    .addHeader("Content-Type", contentType)
                    .build();

            Response okHttpResponse = okHttpClient.newCall(request).execute();
            if (okHttpResponse == null) {
                log.error("OkHttpClient post error,path:{},reqBody:{},result:{}", path, reqBody,
                        null);
                throw new TRetailBizException(BaseErrorCode.REMOTE_CALL_LOST);
            }
            int code = okHttpResponse.code();
            if (!okHttpResponse.isSuccessful()) {
                log.error("OkHttpClient post error,code:{},path:{},reqBody:{},result:{}", code, path, reqBody,
                        okHttpResponse);
                throw new TRetailBizException(BaseErrorCode.REMOTE_CALL_LOST, "response code:" + code);
            }
            String responseBody = okHttpResponse.body().string();
            JSONObject jsonRes = JsonUtils.parse(responseBody, JSONObject.class);
            Integer qufaCode = jsonRes.getInteger("code");
            if (qufaCode == null) {
                log.error("OkHttpClient okhttp post error,path:{},requestBody:{},result:{}", path, reqBody,
                        responseBody);
                throw new TRetailBizException(BaseErrorCode.REMOTE_CALL_LOST, jsonRes.getString("message"));
            } else if (!qufaCode.equals(successCode)) {
                log.error("OkHttpClient okhttp post error,path:{},requestBody:{},result:{}", path, reqBody,
                        responseBody);
                throw new TRetailBizException(BaseErrorCode.REMOTE_CALL_LOST, jsonRes.getString("msg"));
            }
            return jsonRes;

        } catch (Exception e) {
            throwable = e;
            if (e instanceof TRetailBizException) {
                TRetailBizException exp = (TRetailBizException) e;
                throw new TRetailBizException(exp.getErrorCode(), exp.getMessage());
            }
            log.error("OKHttpUtil post error,path:{},reqBody:{}", path, reqBody, e);
            throw new TRetailRpcResultException(BaseErrorCode.REMOTE_CALL_LOST);
        } finally {
            MonitorUtil.getSingleMonitorService().logTransaction("HttpCall", path,
                    startTime, throwable);
        }
    }

    private static String getGMTTime() {
        Calendar cd = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        return sdf.format(cd.getTime());
    }

    private static String sortQueryParams(String queryParam) {
        if (StringUtils.isEmpty(queryParam)) {
            return "";
        }

        Map<String, String> queryPairs = new TreeMap<>();
        String[] queryParams = queryParam.split("&");

        for (String query : queryParams) {
            String[] kv = query.split("=");
            queryPairs.put(kv[0], kv[1]);
        }

        StringBuilder sortedParamsBuilder = new StringBuilder();
        for (Map.Entry<String, String> entry : queryPairs.entrySet()) {
            sortedParamsBuilder.append(entry.getKey());
            sortedParamsBuilder.append("=");
            sortedParamsBuilder.append(entry.getValue());
            sortedParamsBuilder.append("&");
        }
        String sortedParams = sortedParamsBuilder.toString();
        sortedParams = sortedParams.substring(0, sortedParams.length() - 1);

        return sortedParams;
    }

    private static byte[] hmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception {
        SecretKey secretKey = new SecretKeySpec(encryptKey.getBytes(ENCODING), MAC_NAME);
        Mac mac = Mac.getInstance(MAC_NAME);
        mac.init(secretKey);

        byte[] text = encryptText.getBytes(ENCODING);
        return mac.doFinal(text);
    }

    private static String base64Encode(byte[] key) {
        final Base64.Encoder encoder = Base64.getEncoder();
        return encoder.encodeToString(key);
    }

    private static String getMD5(String str) {
        return DigestUtils.md5Hex(str);
    }
}
